<template>
    <!-- 客户端连接失败弹�?-->
    <el-dialog
      v-model="clientTipDialogVisible"
      class="clientTipDialog"
      :title="t('ai.connectFailedTitle')"
      width="400px"
      :close-on-click-modal="false"
    >
      <div class="tipBox">
        <p>{{ t('ai.connectFailedTip') }}</p>
        <p>
          {{ t('ai.connectFailedCheckTip1')
          }}<a
            href="https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3"
            >{{ t('ai.baiduNetdisk') }}</a>
        </p>
        <p>{{ t('ai.connectFailedCheckTip2') }}</p>
        <P>{{ t('ai.connectFailedCheckTip3') }}</P>
        <p>
          {{ t('ai.connectFailedCheckTip4')
          }}<el-button size="small" @click="testConnect">{{
            t('ai.connectionDetection')
          }}</el-button>
        </p>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="clientTipDialogVisible = false">{{
            t('ai.close')
          }}</el-button>
        </div>
      </template>
    </el-dialog>

    <!-- AI内容输入弹窗 -->
    <el-dialog
      v-model="createDialogVisible"
      class="createDialog"
      :title="t('ai.createMindMapTitle')"
      width="450px"
      :close-on-click-modal="false"
      @close="closeAiCreateDialog"
    >
      <div>
        <el-input
          v-model="aiInput"
          type="textarea"
          :rows="5"
          :placeholder="t('ai.createTip')"
        >
        </el-input>
        <div class="tip warning">
          {{ t('ai.importantTip') }}
        </div>
        <div class="tip">
          {{ t('ai.wantModifyAiConfigTip')
          }}<el-button size="small" @click="showAiConfigDialog">{{
            t('ai.modifyAIConfiguration')
          }}</el-button>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeAiCreateDialog">{{
            t('ai.cancel')
          }}</el-button>
          <el-button type="primary" @click="doAiCreate">{{
            t('ai.confirm')
          }}</el-button>
        </div>
      </template>
    </el-dialog>

    <!-- AI生成中透明遮罩 -->
    <div
      ref="aiCreatingMaskRef"
      v-show="aiCreatingMaskVisible"
      class="aiCreatingMask"
    >
      <el-button type="warning" class="btn" @click="stopCreate">{{
        t('ai.stopGenerating')
      }}</el-button>
    </div>

    <!-- AI续写弹窗 -->
    <el-dialog
      v-model="createPartDialogVisible"
      class="createDialog"
      :title="t('ai.aiCreatePart')"
      width="450px"
      :close-on-click-modal="false"
      @close="closeAiCreatePartDialog"
    >
      <el-input
        v-model="aiPartInput"
        type="textarea"
        :rows="5"
      ></el-input>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeAiCreatePartDialog">{{
            t('ai.cancel')
          }}</el-button>
          <el-button type="primary" @click="confirmAiCreatePart">{{
            t('ai.confirm')
          }}</el-button>
        </div>
      </template>
    </el-dialog>

    <!-- AI配置弹窗 -->
    <AiConfigDialog
      v-model="aiConfigDialogVisible"
      @close="aiConfigDialogVisible = false"
    />
  </template>

<script setup>
import { ref, reactive, computed, onMounted, onBeforeUnmount, defineProps } from 'vue'
import { storeToRefs } from 'pinia'
import { useI18n } from 'vue-i18n'
import { useMainStore } from '@/stores'
import { useEventBus } from '@/hooks'
import { ElMessage } from 'element-plus'
import Ai from '@/utils/ai'
import { transformMarkdownTo } from 'simple-mind-map/src/parse/markdownTo'
import {
  createUid,
  isUndef,
  checkNodeOuter,
  getStrWithBrFromHtml
} from 'simple-mind-map/src/utils'
import AiConfigDialog from './AiConfigDialog.vue'

// 定义props
const props = defineProps({
  mindMap: {
    type: Object
  }
})

// 使用store和i18n
const store = useMainStore()
const { aiConfig } = storeToRefs(store)
const { t } = useI18n()
const { $on, $off, $emit, $once } = useEventBus()

// 响应式数�?
const aiCreatingMaskRef = ref(null)
const aiInstance = ref(null)
const isAiCreating = ref(false)
const aiCreatingContent = ref('')

const isLoopRendering = ref(false)
const uidMap = ref({})
const latestUid = ref('')

const clientTipDialogVisible = ref(false)
const createDialogVisible = ref(false)
const aiInput = ref('')
const aiCreatingMaskVisible = ref(false)
const aiConfigDialogVisible = ref(false)

const mindMapDataCache = ref('')
const beingAiCreateNodeUid = ref('')

const createPartDialogVisible = ref(false)
const aiPartInput = ref('')
const beingCreatePartNode = ref(null)

// 方法
const showAiConfigDialog = () => {
  aiConfigDialogVisible.value = true
}

const testConnect = async () => {
  try {
    await fetch(`http://localhost:${aiConfig.value.port}/ai/test`, {
      method: 'GET'
    })
    ElMessage.success(t('ai.connectSuccessful'))
    clientTipDialogVisible.value = false
    createDialogVisible.value = true
  } catch (error) {
    console.log(error)
    ElMessage.error(t('ai.connectFailed'))
  }
}

const aiTest = async () => {
  // 检查配�?  
  if (
    !(
      aiConfig.value.api &&
      aiConfig.value.key &&
      aiConfig.value.model &&
      aiConfig.value.port
    )
  ) {
    showAiConfigDialog()
    throw new Error(t('ai.configurationMissing'))
  }
  // 检查连�?  
  let isConnect = false
  try {
    await fetch(`http://localhost:${aiConfig.value.port}/ai/test`, {
      method: 'GET'
    })
    isConnect = true
  } catch (error) {
    console.log(error)
    clientTipDialogVisible.value = true
  }
  if (!isConnect) {
    throw new Error(t('ai.connectFailed'))
  }
}

const aiCrateAll = async () => {
  try {
    await aiTest()
    createDialogVisible.value = true
  } catch (error) {
    console.log(error)
  }
}

const closeAiCreateDialog = () => {
  createDialogVisible.value = false
  aiInput.value = ''
}

const doAiCreate = () => {
  const aiInputText = aiInput.value.trim()
  if (!aiInputText) {
    ElMessage.warning(t('ai.noInputTip'))
    return
  }
  closeAiCreateDialog()
  aiCreatingMaskVisible.value = true
  // 发起请求
  isAiCreating.value = true
  aiInstance.value = new Ai({
    port: aiConfig.value.port
  })
  aiInstance.value.init('huoshan', aiConfig.value)
  props.mindMap.renderer.setRootNodeCenter()
  props.mindMap.setData(null)
  aiInstance.value.request(
    {
      messages: [
        {
          role: 'user',
          content: `${t(
            'ai.aiCreateMsgPrefix'
          )}${aiInputText}${t('ai.aiCreateMsgPostfix')}`
        }
      ]
    },
    content => {
      if (content) {
        const arr = content.split(/\n+/)
        aiCreatingContent.value = arr.splice(0, arr.length - 1).join('\n')
      }
      loopRenderOnAiCreating()
    },
    content => {
      aiCreatingContent.value = content
      resetOnAiCreatingStop()
    },
    () => {
      resetOnAiCreatingStop()
      resetOnRenderEnd()
      ElMessage.error(t('ai.generationFailed'))
    }
  )
}

const resetOnAiCreatingStop = () => {
  aiCreatingMaskVisible.value = false
  isAiCreating.value = false
  aiInstance.value = null
}

const resetOnRenderEnd = () => {
  isLoopRendering.value = false
  uidMap.value = {}
  aiCreatingContent.value = ''
  mindMapDataCache.value = ''
  beingAiCreateNodeUid.value = ''
}

const stopCreate = () => {
  aiInstance.value.stop()
  isAiCreating.value = false
  aiCreatingMaskVisible.value = false
  ElMessage.success(t('ai.stoppedGenerating'))
}

const loopRenderOnAiCreating = () => {
  if (!aiCreatingContent.value.trim() || isLoopRendering.value) return
  isLoopRendering.value = true
  const treeData = transformMarkdownTo(aiCreatingContent.value)
  addUid(treeData)
  let lastTreeData = JSON.stringify(treeData)

  // 在当前渲染完成时再进行下一次渲�?  
  const onRenderEnd = () => {
    // 处理超出画布的节�?    
  checkNodeOuter()

    // 如果生成结束数据渲染完毕，那么解绑事�?    
    if (!isAiCreating.value && !aiCreatingContent.value) {
      props.mindMap.off('node_tree_render_end', onRenderEnd)
      latestUid.value = ''
      return
    }

    const treeData = transformMarkdownTo(aiCreatingContent.value)
    addUid(treeData)
    // 正在生成�?   
    if (isAiCreating.value) {
      // 如果和上次数据一样则不触发重新渲�?      
      const curTreeData = JSON.stringify(treeData)
      if (curTreeData === lastTreeData) {
        setTimeout(() => {
          onRenderEnd()
        }, 500)
        return
      }
      lastTreeData = curTreeData
      props.mindMap.updateData(treeData)
    } else {
      // 已经生成结束
      // 还要触发一遍渲染，否则会丢失数�?      
      props.mindMap.updateData(treeData)
      resetOnRenderEnd()
      ElMessage.success(t('ai.aiGenerationSuccess'))
    }
  }
  props.mindMap.on('node_tree_render_end', onRenderEnd)

  props.mindMap.setData(treeData)
}

const checkNodeOuter = () => {
  if (latestUid.value) {
    const latestNode = props.mindMap.renderer.findNodeByUid(latestUid.value)
    if (latestNode) {
      const { isOuter, offsetLeft, offsetTop } = checkNodeOuter(
        props.mindMap,
        latestNode,
        100,
        100
      )
      if (isOuter) {
        props.mindMap.view.translateXY(offsetLeft, offsetTop)
      }
    }
  }
}

const addUid = (data) => {
  const checkRepeatUidMap = {}
  const walk = (node, pUid = '') => {
    if (!node.data) {
      node.data = {}
    }
    if (isUndef(node.data.uid)) {
      // 根据pUid+文本内容来复用上一次生成数据的uid
      const key = pUid + '-' + node.data.text
      node.data.uid = uidMap.value[key] || createUid()
      // 当前uid和之前的重复，那么重新生成一个。这种情况很少，但是以防万一
      if (checkRepeatUidMap[node.data.uid]) {
        node.data.uid = createUid()
      }
      latestUid.value = uidMap.value[key] = node.data.uid
      checkRepeatUidMap[node.data.uid] = true
    }
    if (node.children && node.children.length > 0) {
      node.children.forEach(child => {
        walk(child, node.data.uid)
      })
    }
  }
  walk(data)
}

const showAiCreatePartDialog = (node) => {
  beingCreatePartNode.value = node
  const currentMindMapData = props.mindMap.getData()
  // 填充默认内容
  aiPartInput.value = `${t(
    'ai.aiCreatePartMsgPrefix'
  )}${getStrWithBrFromHtml(currentMindMapData.data.text)}${t(
    'ai.aiCreatePartMsgCenter'
  )}${getStrWithBrFromHtml(node.getData('text'))}${t(
    'ai.aiCreatePartMsgPostfix'
  )}`
  createPartDialogVisible.value = true
}

const closeAiCreatePartDialog = () => {
  createPartDialogVisible.value = false
}

const resetAiCreatePartDialog = () => {
  beingCreatePartNode.value = null
  aiPartInput.value = ''
}

const confirmAiCreatePart = () => {
  if (!aiPartInput.value.trim()) return
  closeAiCreatePartDialog()
  aiCreatePart()
}

const aiCreatePart = async () => {
  try {
    if (!beingCreatePartNode.value) {
      return
    }
    await aiTest()
    beingAiCreateNodeUid.value = beingCreatePartNode.value.getData('uid')
    const currentMindMapData = props.mindMap.getData()
    mindMapDataCache.value = JSON.stringify(currentMindMapData)
    aiCreatingMaskVisible.value = true
    // 发起请求
    isAiCreating.value = true
    aiInstance.value = new Ai({
      port: aiConfig.value.port
    })
    aiInstance.value.init('huoshan', aiConfig.value)
    aiInstance.value.request(
      {
        messages: [
          {
            role: 'user',
            content:
              aiPartInput.value.trim() + t('ai.aiCreatePartMsgHelp')
          }
        ]
      },
      content => {
        if (content) {
          const arr = content.split(/\n+/)
          aiCreatingContent.value = arr.splice(0, arr.length - 1).join('\n')
        }

        loopRenderOnAiCreatingPart()
      },
      content => {
        aiCreatingContent.value = content
        resetOnAiCreatingStop()
        resetAiCreatePartDialog()
      },
      () => {
        resetOnAiCreatingStop()
        resetAiCreatePartDialog()
        resetOnRenderEnd()
        ElMessage.error(t('ai.generationFailed'))
      }
    )
  } catch (error) {
    console.log(error)
  }
}

const addToTargetNode = (newChildren = []) => {
  const initData = JSON.parse(mindMapDataCache.value)
  const walk = node => {
    if (node.data.uid === beingAiCreateNodeUid.value) {
      if (!node.children) {
        node.children = []
      }
      node.children.push(...newChildren)
      return
    }
    if (node.children && node.children.length > 0) {
      node.children.forEach(child => {
        walk(child)
      })
    }
  }
  walk(initData)
  return initData
}

const loopRenderOnAiCreatingPart = () => {
  if (!aiCreatingContent.value.trim() || isLoopRendering.value) return
  isLoopRendering.value = true
  const partData = transformMarkdownTo(aiCreatingContent.value)
  addUid(partData)
  let lastPartData = JSON.stringify(partData)
  const treeData = addToTargetNode(partData.children || [])

  // 在当前渲染完成时再进行下一次渲�?  
  const onRenderEnd = () => {
    // 处理超出画布的节�?    
    checkNodeOuter()

    // 如果生成结束数据渲染完毕，那么解绑事�?   
     if (!isAiCreating.value && !aiCreatingContent.value) {
      props.mindMap.off('node_tree_render_end', onRenderEnd)
      latestUid.value = ''
      return
    }

    const partData = transformMarkdownTo(aiCreatingContent.value)
    addUid(partData)
    const treeData = addToTargetNode(partData.children || [])

    if (isAiCreating.value) {
      // 如果和上次数据一样则不触发重新渲�?     
     const curPartData = JSON.stringify(partData)
      if (curPartData === lastPartData) {
        setTimeout(() => {
          onRenderEnd()
        }, 500)
        return
      }
      lastPartData = curPartData
      props.mindMap.updateData(treeData)
    } else {
      props.mindMap.updateData(treeData)
      resetOnRenderEnd()
      ElMessage.success(t('ai.aiGenerationSuccess'))
    }
  }
  props.mindMap.on('node_tree_render_end', onRenderEnd)
  // 因为是续写，所以首次也直接使用updateData方法渲染
  props.mindMap.updateData(treeData)
}

const aiChat = async (
  messageList = [],
  progress = () => {},
  end = () => {},
  err = () => {}
) => {
  try {
    await aiTest()
    // 发起请求
    isAiCreating.value = true
    aiInstance.value = new Ai({
      port: aiConfig.value.port
    })
    aiInstance.value.init('huoshan', aiConfig.value)
    aiInstance.value.request(
      {
        messages: messageList.map(msg => {
          return {
            role: 'user',
            content: msg
          }
        })
      },
      content => {
        progress(content)
      },
      content => {
        end(content)
      },
      error => {
        err(error)
      }
    )
  } catch (error) {
    console.log(error)
  }
}

const aiChatStop = () => {
  if (aiInstance.value) {
    aiInstance.value.stop()
    isAiCreating.value = false
    aiInstance.value = null
  }
}

// 生命周期钩子
onMounted(() => {
  $on('ai_create_all', aiCrateAll)
  $on('ai_create_part', showAiCreatePartDialog)
  $on('ai_chat', aiChat)
  $on('ai_chat_stop', aiChatStop)
  $on('showAiConfigDialog', showAiConfigDialog)
  
  document.body.appendChild(aiCreatingMaskRef.value)
})

onBeforeUnmount(() => {
  $off('ai_create_all', aiCrateAll)
  $off('ai_create_part', showAiCreatePartDialog)
  $off('ai_chat', aiChat)
  $off('ai_chat_stop', aiChatStop)
  $off('showAiConfigDialog', showAiConfigDialog)
})
</script>

<style lang="less" scoped>
.clientTipDialog,
.createDialog {
  :deep(.el-dialog__body) {
    padding: 12px 20px;
  }
}

.tipBox {
  p {
    margin-bottom: 12px;

    a {
      color: #409eff;
    }
  }
}

.inputBox {
  .tip {
    margin-top: 12px;

    &.warning {
      color: #f56c6c;
    }
  }
}

.aiCreatingMask {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  z-index: 99999;
  background-color: transparent;

  .btn {
    position: absolute;
    left: 50%;
    top: 100px;
    transform: translateX(-50%);
  }
}
</style>
